Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 | /**
* Input Validation Middleware
*
* Comprehensive validation rules using express-validator
* for all API endpoints to ensure data integrity and security
*/
const { body, param, query, validationResult } = require('express-validator');
const validator = require('validator');
/**
* Middleware to handle validation results
* Returns 400 with validation errors if validation fails
*/
const handleValidationErrors = (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
const formattedErrors = errors.array().map(err => ({
field: err.path || err.param,
message: err.msg,
value: err.value
}));
return res.status(400).json({
success: false,
error: 'Validation failed',
details: formattedErrors
});
}
next();
};
/**
* Common validation rules that can be reused
*/
const commonValidations = {
// Username validation
username: body('username')
.trim()
.notEmpty().withMessage('Username is required')
.isLength({ min: 3, max: 50 }).withMessage('Username must be between 3 and 50 characters')
.matches(/^[a-zA-Z0-9_]+$/).withMessage('Username can only contain letters, numbers, and underscores'),
// Password validation
password: body('password')
.notEmpty().withMessage('Password is required')
.isLength({ min: 6 }).withMessage('Password must be at least 6 characters long'),
// Email validation
email: body('email')
.optional()
.trim()
.isEmail().withMessage('Invalid email format')
.normalizeEmail(),
// Phone number validation
phone: body('phone')
.optional()
.trim()
.matches(/^[\d\s\-\+\(\)]+$/).withMessage('Invalid phone number format')
.isLength({ min: 10, max: 20 }).withMessage('Phone number must be between 10 and 20 characters'),
// Text/Message validation
message: body('message')
.trim()
.notEmpty().withMessage('Message is required')
.isLength({ max: 5000 }).withMessage('Message must not exceed 5000 characters'),
// ID parameter validation
id: param('id')
.isInt({ min: 1 }).withMessage('Invalid ID format'),
// Pagination
page: query('page')
.optional()
.isInt({ min: 1 }).withMessage('Page must be a positive integer')
.toInt(),
limit: query('limit')
.optional()
.isInt({ min: 1, max: 100 }).withMessage('Limit must be between 1 and 100')
.toInt()
};
/**
* Custom sanitization middleware
* Removes potentially dangerous characters from inputs
*/
const sanitizeInput = (req, res, next) => {
// Sanitize body
if (req.body && typeof req.body === 'object') {
Object.keys(req.body).forEach(key => {
if (typeof req.body[key] === 'string') {
// Remove null bytes
req.body[key] = req.body[key].replace(/\0/g, '');
// For fields that shouldn't contain HTML, strip tags
if (!['message', 'answer', 'description'].includes(key)) {
req.body[key] = validator.stripLow(req.body[key]);
}
}
});
}
// Sanitize query parameters
if (req.query && typeof req.query === 'object') {
Object.keys(req.query).forEach(key => {
if (typeof req.query[key] === 'string') {
req.query[key] = req.query[key].replace(/\0/g, '');
req.query[key] = validator.stripLow(req.query[key]);
}
});
}
next();
};
module.exports = {
// Validation result handler
handleValidationErrors,
// Common validations
commonValidations,
// Sanitization
sanitizeInput
};
|